package com.gzyct.m.api.common.sign;

import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gzyct.m.api.common.biz.req.BizRequest;
import com.gzyct.m.api.common.intf.req.InterfaceRequest;
import com.gzyct.m.api.common.intf.resp.InterfaceResp;
import com.gzyct.m.api.common.util.JacksonObjectMapper;
import com.gzyct.m.api.common.util.JacksonUtils;

public abstract class AbstractSigner implements Signer {

	protected final Logger logger = LoggerFactory.getLogger(getClass());

	private SignKeyStore signKeyStore = new DefaultSignKeyStore();
	
	protected ThreadLocal<String> threadLocalsignKey = new ThreadLocal<String>();

	@Override
	public boolean verifySign(InterfaceRequest ir) throws Exception {

		String signKey = getKey(ir.getChannel_code(), ir.getSign_type());
		
		threadLocalsignKey.set(signKey);
		
		String signString = wrapRequestSignString(buildRequestSignString(ir), ir, signKey);
		String charset = ir.getCharset();
		String signed = doSign(signString.getBytes(charset));
		String requestSign = ir.getSign();
		boolean valid = signed.equalsIgnoreCase(requestSign);
		logger.info(
				"VerifySign request result : {}; signed output : {}, request original sign : {}, signString : {} ===== for interfaceRequest {}",
				valid, signed, requestSign, signString, ir);
		return valid;

	}

	private String buildRequestSignString(InterfaceRequest ir) {
		return SignStringBuildWorker.requestBuild(ir);
	}

	protected String getKey(String channelCode, String signType) throws Exception {
		String key = signKeyStore.getSignKey(channelCode, SignType.valueOf(signType));
		if (key == null) {
			throw new RuntimeException("no key found for channelCode " + channelCode + " and signType "
					+ signType);
		} else {
			return key;
		}
	}

	abstract protected String wrapRequestSignString(String signString, InterfaceRequest ir, String signKey)
			throws Exception;

	abstract protected String wrapSignString(String signString, String channelCode, String signType,
			String signKey) throws Exception;

	abstract protected String wrapRespSignString(String signString, InterfaceResp ir, String signKey)
			throws Exception;

	abstract protected String doSign(byte[] bytes) throws Exception;

	@Override
	public InterfaceResp sign(InterfaceResp ir) throws Exception {
	
		String signKey = getKey(ir.getChannel_code(), ir.getSign_type());
		
		threadLocalsignKey.set(signKey);
		
		String signString = wrapRespSignString(buildRespSignString(ir), ir, signKey);
		String charset = ir.getCharset();
		String signed = doSign(signString.getBytes(charset));
		logger.info("Sign response : signed output : {}, signString : {} ===== for interfaceResp {}", signed,
				signString, ir);
		ir.setSign(signed);
		SignStringBuildWorker.updateSign(ir, signed);
		return ir;
	}

	@Override
	public BizRequest sign(BizRequest req) throws Exception {
		String signKey = getKey(req.getChannel_code(), req.getSign_type());
		return sign0(req, signKey);
	}

	private BizRequest sign0(BizRequest req, String signKey) throws Exception {
		String signString = signString(req);
		signString = wrapSignString(signString, req.getChannel_code(), req.getSign_type(), signKey);
		String charset = req.getCharset();
		String signed = doSign(signString.getBytes(charset));
		logger.info("Sign bizRequest : signed output : {}, signString : {} ===== for bizRequest {}", signed,
				signString, req);
		req.setSign(signed);
		return req;
	}

	@Override
	public BizRequest sign(BizRequest req, String signKey) throws Exception {
		return sign0(req, signKey);
	}

	public static String signString(Object obj) throws Exception {

		ObjectMapper mapper = JacksonObjectMapper.getDefault();
		String json = mapper.writeValueAsString(obj);
		JsonNode root = mapper.readTree(json.getBytes("UTF-8"));
		Map<String, String> map = new TreeMap<String, String>();
		for (Iterator<Entry<String, JsonNode>> it = root.fields(); it.hasNext();) {
			Entry<String, JsonNode> entry = it.next();
			JsonNode node = entry.getValue();
			map.put(entry.getKey(), JacksonUtils.getNodeStringValue(node));
		}
		map.remove(InterfaceRequest.SIGN);
		StringBuilder sb = new StringBuilder();
		for (Map.Entry<String, String> entry : map.entrySet()) {
			if (entry.getValue() != null && !"".equals(entry.getValue())) {
				sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
			}
		}
		return sb.substring(0, sb.length() - 1).toString();
	}

	private String buildRespSignString(InterfaceResp ir) {
		return SignStringBuildWorker.respBuild(ir);
	}

}
